package enums

import (
	"fmt"
	"reflect"
	"strconv"
	"strings"
)

type Enum struct {
	Value       int
	Name        string
	Description string
}

type Enums struct {
	names        map[string]Enum
	values       map[int]Enum
	descriptions map[string]Enum
}

func (es Enums) String() string {
	return fmt.Sprintf("Enums: \nnames: %v\nvalues:%v\ndesciptions:%v", es.names, es.values, es.descriptions)
}

func (es *Enums) FromName(name string) *Enum {
	e, ok := es.names[strings.ToLower(name)]
	if ok {
		return &e
	}
	return nil
}

func (es *Enums) FromValue(value int) *Enum {
	e, ok := es.values[value]
	if ok {
		return &e
	}
	return nil
}

func (es *Enums) AtoI(name string) int {
	e := es.FromName(name)
	if e == nil {
		return 0
	}
	return e.Value
}

func (es *Enums) ItoA(value int) string {
	e := es.FromValue(value)
	if e == nil {
		return ""
	}
	return e.Name
}

func NewEnumerate(enumStruct interface{}) interface{} {
	return newEnumerate(enumStruct)
}

func newEnumerate(enumStruct interface{}) interface{} {
	structValue := reflect.ValueOf(enumStruct).Elem()
	structType := reflect.TypeOf(enumStruct).Elem()
	enumType := reflect.TypeOf(Enum{})
	enumsType := reflect.TypeOf(Enums{})
	nameMap, valueMap, descriptionMap := map[string]Enum{}, map[int]Enum{}, map[string]Enum{}

	for i := 0; i < structValue.NumField(); i++ {
		t := structType.Field(i)
		v := structValue.Field(i)
		switch t.Type {
		case enumType:
			setEnumName(v, t)
			setEnumValue(v, t, i)
			setEnumDescription(v, t)
			enum := v.Interface().(Enum)
			nameMap[strings.ToLower(enum.Name)] = enum
			valueMap[enum.Value] = enum
			descriptionMap[enum.Description] = enum
			break
		case enumsType:
			enums := v.Interface().(Enums)
			enums.values = valueMap
			enums.names = nameMap
			enums.descriptions = descriptionMap
			v.Set(reflect.ValueOf(enums))
			break
		}
	}
	return enumStruct
}

func setEnumName(enumValue reflect.Value, enumType reflect.StructField) {
	enumValue.FieldByName("Name").SetString(enumType.Name)
}

func setEnumValue(enumValue reflect.Value, enumType reflect.StructField, index int) {
	var err error
	value := -1
	valueStr, ok := enumType.Tag.Lookup("val")
	if ok {
		value, err = strconv.Atoi(valueStr)
		if err != nil {
			value = -1
		}
	}
	if value < 0 {
		value = index
	}
	enumValue.FieldByName("Value").SetInt(int64(value))
}

func setEnumDescription(enumValue reflect.Value, enumType reflect.StructField) {
	desc, _ := enumType.Tag.Lookup("desc")
	enumValue.FieldByName("Description").SetString(desc)
}
